import json

from ..core import Provider
from ..core import Response
from ..utils import requests
from ..utils.schema.helpers import one_or_more


class MailGun(Provider):
    """Send emails via MailGun"""

    base_url = "https://api.mailgun.net/v3/{domain}/messages"
    site_url = "https://documentation.mailgun.com/"
    name = "mailgun"
    path_to_errors = ("message",)

    __properties_to_change = [
        "tag",
        "dkim",
        "deliverytime",
        "testmode",
        "tracking",
        "tracking_clicks",
        "tracking_opens",
        "require_tls",
        "skip_verification",
    ]

    __email_list = one_or_more(
        {
            "type": "string",
            "title": 'Email address of the recipient(s). Example: "Bob <bob@host.com>".',
        }
    )

    _required = {
        "allOf": [
            {"required": ["to", "domain", "api_key"]},
            {"anyOf": [{"required": ["from"]}, {"required": ["from_"]}]},
            {
                "anyOf": [{"required": ["message"]}, {"required": ["html"]}],
                "error_anyOf": 'Need either "message" or "html"',
            },
        ]
    }

    _schema = {
        "type": "object",
        "properties": {
            "api_key": {"type": "string", "title": "User's API key"},
            "message": {
                "type": "string",
                "title": "Body of the message. (text version)",
            },
            "html": {"type": "string", "title": "Body of the message. (HTML version)"},
            "to": __email_list,
            "from": {
                "type": "string",
                "format": "email",
                "title": "Email address for From header",
            },
            "from_": {
                "type": "string",
                "format": "email",
                "title": "Email address for From header",
                "duplicate": True,
            },
            "domain": {"type": "string", "title": "MailGun's domain to use"},
            "cc": __email_list,
            "bcc": __email_list,
            "subject": {"type": "string", "title": "Message subject"},
            "attachment": one_or_more(
                {"type": "string", "format": "valid_file", "title": "File attachment"}
            ),
            "inline": one_or_more(
                {
                    "type": "string",
                    "format": "valid_file",
                    "title": "Attachment with inline disposition. Can be used to send inline images",
                }
            ),
            "tag": one_or_more(
                schema={
                    "type": "string",
                    "format": "ascii",
                    "title": "Tag string",
                    "maxLength": 128,
                },
                max=3,
            ),
            "dkim": {
                "type": "boolean",
                "title": "Enables/disables DKIM signatures on per-message basis",
            },
            "deliverytime": {
                "type": "string",
                "format": "rfc2822",
                "title": "Desired time of delivery. Note: Messages can be scheduled for a maximum of 3 days in "
                "the future.",
            },
            "testmode": {"type": "boolean", "title": "Enables sending in test mode."},
            "tracking": {
                "type": "boolean",
                "title": "Toggles tracking on a per-message basis",
            },
            "tracking_clicks": {
                "type": ["string", "boolean"],
                "title": "Toggles clicks tracking on a per-message basis. Has higher priority than domain-level"
                " setting. Pass yes, no or htmlonly.",
                "enum": [True, False, "htmlonly"],
            },
            "tracking_opens": {
                "type": "boolean",
                "title": "Toggles opens tracking on a per-message basis. Has higher priority than domain-level setting",
            },
            "require_tls": {
                "type": "boolean",
                "title": "If set to True this requires the message only be sent over a TLS connection."
                " If a TLS connection can not be established, Mailgun will not deliver the message."
                "If set to False, Mailgun will still try and upgrade the connection, but if Mailgun can not,"
                " the message will be delivered over a plaintext SMTP connection.",
            },
            "skip_verification": {
                "type": "boolean",
                "title": "If set to True, the certificate and hostname will not be verified when trying to establish "
                "a TLS connection and Mailgun will accept any certificate during delivery. If set to False,"
                " Mailgun will verify the certificate and hostname. If either one can not be verified, "
                "a TLS connection will not be established.",
            },
            "headers": {
                "type": "object",
                "additionalProperties": {"type": "string"},
                "title": "Any other header to add",
            },
            "data": {
                "type": "object",
                "additionalProperties": {"type": "object"},
                "title": "attach a custom JSON data to the message",
            },
        },
        "additionalProperties": False,
    }

    def _prepare_data(self, data: dict) -> dict:
        if data.get("from_"):
            data["from"] = data.pop("from_")

        new_data = {
            "to": data.pop("to"),
            "from": data.pop("from"),
            "domain": data.pop("domain"),
            "api_key": data.pop("api_key"),
        }

        if data.get("message"):
            new_data["text"] = data.pop("message")

        if data.get("attachment"):
            attachment = data.pop("attachment")
            if isinstance(attachment, str):
                attachment = [attachment]
            new_data["attachment"] = attachment

        if data.get("inline"):
            inline = data.pop("inline")
            if isinstance(inline, str):
                inline = [inline]
            new_data["inline"] = inline

        for property_ in self.__properties_to_change:
            if data.get(property_):
                new_property = f"o:{property_}".replace("_", "-")
                new_data[new_property] = data.pop(property_)

        if data.get("headers"):
            for key, value in data["headers"].items():
                new_data[f"h:{key}"] = value
            del data["headers"]

        if data.get("data"):
            for key, value in data["data"].items():
                new_data[f"v:{key}"] = json.dumps(value)
            del data["data"]

        for key, value in data.items():
            new_data[key] = value

        return new_data

    def _send_notification(self, data: dict) -> Response:
        url = self.base_url.format(domain=data.pop("domain"))
        auth = "api", data.pop("api_key")
        files = []
        if data.get("attachment"):
            files += requests.file_list_for_request(data["attachment"], "attachment")
        if data.get("inline"):
            files += requests.file_list_for_request(data["inline"], "inline")

        response, errors = requests.post(
            url=url,
            data=data,
            auth=auth,
            files=files,
            path_to_errors=self.path_to_errors,
        )
        return self.create_response(data, response, errors)
